﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.WowAddonStudio.Lua.Model
{
    /// <summary>
    /// A Lua <see cref="Scope"/> that holds <see cref="Documentation"/>.
    /// </summary>
    public class DocumentationScope : Scope
    {
        private readonly Documentation documentation;

        /// <summary>
        /// Initializes a new instance of the <see cref="DocumentationScope"/> class.
        /// </summary>
        /// <param name="documentation"></param>
        /// <param name="parentScope">The parent scope.</param>
        public DocumentationScope(Documentation documentation, Scope parentScope)
            : base(parentScope)
        {
            if (documentation == null)
                throw new ArgumentNullException("documentation");

            this.documentation = documentation;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DocumentationScope"/> class.
        /// </summary>
        /// <param name="documentation">The documentation.</param>
        public DocumentationScope(Documentation documentation)
            : this(documentation, null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DocumentationScope"/> class.
        /// </summary>
        /// <param name="parentScope">The parent scope.</param>
        public DocumentationScope(Scope parentScope)
            : this(new Documentation(), parentScope)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DocumentationScope"/> class.
        /// </summary>
        public DocumentationScope()
            : this(new Documentation(), null)
        {
        }

        /// <summary>
        /// Returns the global scope that contains this scope.
        /// </summary>
        public new DocumentationScope GlobalScope
        {
            get { return base.GlobalScope as DocumentationScope; }
        }

        /// <summary>
        /// Gets the documentation for the scope.
        /// </summary>
        public Documentation Documentation
        {
            get { return documentation; }
        }

        /// <summary>
        /// Gets the declarations in the current scope and its parent scopes.
        /// </summary>
        public IEnumerable<Declaration> Declarations
        {
            get
            {
                if (Parent != null && Parent is DocumentationScope)
                    return this.Documentation.Declarations.Concat(((DocumentationScope)Parent).Declarations);

                return this.Documentation.Declarations;
            }
        }

        /// <summary>
        /// Clears the documentation of the scope and its children.
        /// </summary>
        public void Clear()
        {
            this.Documentation.Clear();

            foreach (DocumentationScope childScope in this.ChildScopes)
                childScope.Clear();
        }

        /// <summary>
        /// Adds a declaration to the documentation.
        /// </summary>
        /// <param name="declaration">The declaration.</param>
        public void Add(Declaration declaration)
        {
            if (declaration == null) 
                throw new ArgumentNullException("declaration");

            if (declaration.IsQualified)
            {
                var qualifiedTable = this.Resolve<Table>(declaration.QualifiedName.Unqualify(true));
                if (qualifiedTable != null)
                    qualifiedTable.Declarations.Add(declaration);
            }
            else
            {
                if (!this.Documentation.Contains(declaration))
                {
                    if (declaration.IsLocal)
                        this.Documentation.Add(declaration);
                    else
                        this.GlobalScope.Documentation.Add(declaration);
                }
            }
        }

        /// <summary>
        /// Adds a range of declarations to the documentation.
        /// </summary>
        /// <param name="declarations">The enumerable collection of declarations to add.</param>
        public void AddRange(IEnumerable<Declaration> declarations)
        {
            if (declarations == null)
                throw new ArgumentNullException("declarations");

            foreach (var declaration in declarations)
                this.Add(declaration);
        }

        /// <summary>
        /// Resolve a declaration in the scope.
        /// </summary>
        /// <param name="qualifiedName">The qualified name of the declaration.</param>
        /// <returns>An instance of the <see cref="Declaration"/> class if found; null otherwise.</returns>
        public TDeclaration Resolve<TDeclaration>(QualifiedName qualifiedName) where TDeclaration : Declaration
        {
            if (qualifiedName == null)
                throw new ArgumentNullException("qualifiedName");

            var currentScope = this;
            while (currentScope != null)
            {
                var declaration = currentScope.Documentation.Resolve<TDeclaration>(qualifiedName);
                if (declaration != null)
                    return declaration;

                currentScope = currentScope.Parent as DocumentationScope;
            }

            return null;
        }

        /// <summary>
        /// Merges a scope and its child scopes into the scope.
        /// </summary>
        /// <param name="scope">The scope to merge.</param>
        public override void Merge(Scope scope)
        {
            if (!(scope is DocumentationScope))
                throw new ArgumentException("Scope is not a DocumentationScope.");

            base.Merge(scope);

            var documentationScope = (DocumentationScope)scope;

            this.Documentation.Merge(documentationScope.Documentation);
            this.Documentation.MergePrototypes();
        }
    }
}
